<?php

namespace app\command;

use app\helper\FastRequest;

use app\helper\TorrentHelper;
use GuzzleHttp\Client;
use GuzzleHttp\Pool;
use GuzzleHttp\Psr7\Request;

use app\helper\ProcessLockHelper;
use app\api\QbApi;
use app\api\TrApi;

use app\model\Crontab;
use app\model\CrontabLog;
use app\model\Download;
use app\model\Sites;
use app\model\TorrentBank;
use app\model\TorrentCache;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
 * 内置辅种代码，通过指令运行，传递数据库中任务ID即可
 */
class TaskReseed extends Command
{
  //任务指令
  protected static $defaultName = 'task:reseed';
  //任务描述
  protected static $defaultDescription = '内置辅种模式任务';
  //全局化日志ID
  private $logId;

  /**
   * @return void
   */
  protected function configure()
  {
    $this->addArgument('id', InputArgument::OPTIONAL, '参数传递');
  }

  /**
   * @param InputInterface $input
   * @param OutputInterface $output
   */
  protected function execute(InputInterface $input, OutputInterface $output): int
  {
    // 定义全局日志ID
    $this->logId = $input->getArgument('id');
    // 获取辅种任务中的下载器IDS
    $downloadIds = json_decode(Crontab::where('id', $this->logId)->find()['parameter']);
    
    $this->writeLog(['exception' => '———————————辅种任务开始———————————']);
    // 获取辅种任务锁
    $reseedLock = new ProcessLockHelper(2);
    if ($reseedLock->isLockExists()) {
      $this->writeLog(['exception' => "辅种任务已启动，请勿重复运行"]);
      return 0;
    }
    // 创建任务锁
    $reseedLock->createLock();
    // 执行种子库计算
    $torrentHelper = new TorrentHelper();
    $torrentHelper->index($this->logId);
    // 循环下载器，支持多下载器执行辅种任务
    try {
      foreach ($downloadIds as $downloadId) {
        // 查询当前下载器ID的数据库信息
        $downloadResult = Download::where('id', $downloadId)->find();
        // 判断下载器状态，如果下载器被删除，则跳过
        if (!$downloadResult) {
          $msg = 'id：' . $downloadId . '下载器不存在或已删除，请重新编辑定时任务中的下载器选项';
          $this->writeLog(['exception' => $msg]);
          continue;
        }
        // 进行日志写入
        $msg = '正在执行' . $downloadResult['name'] . '下载器的辅种任务';
        $this->writeLog(['exception' => $msg]);

        // 通过检索种子数据库判断映射目录是否正确
        $msg = '正在检索种子目录...';
        $this->writeLog(['exception' => $msg]);
        $torrents = TorrentBank::where('download_id', $downloadId)->select();
        if (count($torrents) == 0) {
          $msg = '种子库数量为0，将查看系统日志或者手动执行种子库任务';
          $this->writeLog(['exception' => $msg]);
          continue;
        } else {
          $msg = '目录匹配正确，正在开始辅种任务';
          $this->writeLog(['exception' => $msg]);
        }
        // 查询当前下载器配置的参与辅种的站点信息
        $siteResult = Sites::whereIn('id', array_map('intval', explode(',', $downloadResult['reseed_sites'])))->select();
        // 将未配置passkey的站点从辅种列表中剔除
        foreach ($siteResult as $key => $site) {
          if (!isset($site->passkey) || $site->passkey == '') {
            $this->writeLog(['exception' => $site->site_name . '存在于下载器辅种列表，但未配置passkey']);
            unset($siteResult[$key]);
          }
        }
        // 如果下载器中辅种站点为空，则跳过该下载器
        if (count($siteResult) == 0) {
          $msg = '下载器的辅种站点为空，请到下载器中配置对应辅种站点';
          $this->writeLog(['exception' => $msg]);
          continue;
        }

        //定义一个guzzle/client对象
        $client = new Client([
          'verify' => false,
          'timeout' => 30,
          'allow_redirects' => false
        ]);

        // 获取种子缓存库对象
        $torrent_site_cache = TorrentCache::select();
        // 将种子缓存库对象转换成数组，其中只需要info_hash字段
        $torrent_site_cache = array_column($torrent_site_cache->toArray(), 'info_hash');
        // 将辅种成功过的种子从待辅种列表中剔除，然后分为每100个种子一组
        TorrentBank::where('download_id', $downloadId)
          ->where('info_hash', 'not in', $torrent_site_cache)
          ->chunk(100, function ($torrents) use ($siteResult, $downloadResult, &$requests, &$cache_array, $client) {
            // 计时器启动
            $start_time = microtime(true);
            // guzzle request对象数组
            $requests = [];
            // 待辅种种子pieces_hash数组
            $piecesGroup = [];
            // 遍历块中的每个值  
            foreach ($torrents as $torrent) {
              $piecesGroup[] = $torrent->pieces_hash;
            }
            // 每100个种子并发请求已配置passkey的站点
            foreach ($siteResult as $site) {
              // 定义请求头
              $headers = [
                'Accept' => 'application/json',
                'User-Agent' => 'ReseedPuppy/1.0.0',
                'Content-Type' => 'application/json',
                'Connection' => 'keep-alive'
              ];
              // 定义请求体
              $body = ['passkey' => $site->passkey, 'pieces_hash' => $piecesGroup];
              // Request对象包含请求方式、请求地址、请求头、请求体 
              $requests[] = new Request(
                'POST',
                $site->api_url,
                $headers,
                json_encode($body)
              );
            }
            // 辅种成功后站点返回的对应种子ID的数组，顺便作为缓存插入到数据库中
            $cache_array_all = [];
            // 定义并发请求，包含定义的clinet对应和requests对象数组
            $pool = new Pool($client, $requests, [
              // 并发请求的数量
              'concurrency' => 40,
              // 成功请求回调
              'fulfilled' => function ($response, $index) use ($requests,&$downloadResult, &$cache_array_all, $start_time) {
              // 辅种成功后站点返回的对应种子ID的数组，顺便作为缓存插入到数据库中
              $cache_array = [];
              // 获取request请求对象中的请求地址，通过请求地址查询对应站点对象
              $request = $requests[$index];
              $site = Sites::where('api_url', $request->getUri())->findOrEmpty();
              // 返回状态200则执行之后的操作
              if ($response->getStatusCode() == 200) {
                // 获取返回主体
                $results = json_decode($response->getBody()->getContents(), true);
                if ($results['ret'] == 0) {
                  // 循环返回值中的种子ID部分
                  foreach ($results['data'] as $key => $value) {
                    // 将站点返回的pieces_hash在本地种子库中查询保存地址、等信息
                    $flag = TorrentBank::where('pieces_hash', $key)->findOrEmpty();
                    if ($flag) {
                      $value = [
                        'info_hash' => $flag->info_hash,
                        'sid' => $site->id,
                        'download_url' => $site->site_url . '/download.php?id=' . $value . '&passkey=' . $site->passkey,
                        'save_path' => $flag->save_path
                      ];
                      array_push($cache_array, $value);
                      $downloadResult->type == 1 ? self::qb($downloadResult, $site, $value['download_url'], $value['save_path']) : self::tr($downloadResult, $site, $value['download_url'], $value['save_path']);

                    }
                  }
                }
                $cache_array_all = array_merge($cache_array_all, $cache_array);
                // 定义种子缓存库
                $torrent_cache = new TorrentCache;
                // 将辅种成功的种子保存到辅种缓存库中
                $torrent_cache->saveAll($cache_array);
                // 结束计时
                $end_time = microtime(true);
                $execution_time = number_format((float) ($end_time - $start_time), 2);
                // 获取种子缓存库中对应站点的所有种子
                $torrent_site_cache = TorrentCache::where('sid', $site->id)->select();
                $this->writeLog(['exception' => '【耗时' . $execution_time . '秒】' . $site->site_name . '获取辅种到了' . count($cache_array) . '个种子,缓存数据库中存在' . count($torrent_site_cache) . '个种子']);
              }
            },
              'rejected' => function ($reason, $index) {
              // 请求失败时的回调  
              $this->writeLog(['exception' => 'Rejected request ' . $index . ': ' . $reason->getMessage()]);
            },
            ]);
            // 等待所有请求完成
            $promise = $pool->promise();
            $promise->wait();
            $msg = '本轮总计获取到：' . count($cache_array_all) . '个种子';
            $this->writeLog(['exception' => $msg]);
          });
        $reseedLock->removeLock();
      }
      $reseedLock->removeLock();
    } catch (\Exception $e) {
      $this->writeLog(['exception' => $e->getMessage()]);
      $reseedLock->removeLock();
    }
    $this->writeLog(['exception' => '———————————辅种完成———————————']);
    return 1;
  }
  /**
   * qb
   * @param mixed $downloadResult             下载器信息
   * @param mixed $site                       站点信息
   * @param string $download_url              种子下载地址
   * @param string $save_path                 种子保存地址
   * @return int
   */
  private function qb(
    $downloadResult,
    $site,
    $download_url,
    $save_path
  ) {
    $qbapi = new QbApi($downloadResult);
    // $msg = '保存目录：' . $save_path . "下载站点：" . $site['site_url'];
    // $this->writeLog(['exception' => $msg]);
    if (
      $qbapi->addTorrent(
        $download_url,
        $save_path,
        $downloadResult['is_skip_hash'],
        $downloadResult['is_paused'],
        $site['download_limit'],
        $site['upload_limit'],
        'RP'
      ) == 'Ok.'
    ) {
      return 1;
    }
    return 0;
  }

  /**
   * tr
   * @param mixed $downloadResult             下载器信息
   * @param mixed $site                       站点信息
   * @param string $download_url              种子下载地址
   * @param string $save_path                 种子保存地址
   * @return int
   */
  private function tr(
    $downloadResult,
    $site,
    $download_url,
    $save_path
  ) {
    $trapi = new TrApi($downloadResult);

    $result = $trapi->addTorrent(
      $download_url,
      $save_path,
      $downloadResult['is_paused']
    );
    // 下载器已存在的也是成功，但$result['arguments']['torrent-duplicate']有值
    if ($result['result'] == 'success') {
      return 1;
    }
    return 0;
  }

  /**
   * 日志写入方法
   */
  public function writeLog($content)
  {
    $content['crontab_id'] = $this->logId;
    $cronlog = new CrontabLog;
    $cronlog->save($content);
  }

}
